// Fill out your copyright notice in the Description page of Project Settings.


#include "PlayerBaseCpp.h"
#include "NPCBaseCpp.h"
#include "SmallGoblinBaseCpp.h"
#include "FlyingEyeBaseCpp.h"
#include "GameModeBaseCpp.h"
#include "InventoryComponentBaseCpp.h"
#include "AttackComponentBaseCpp.h"
#include "HPComponentBaseCpp.h"
#include "PlayerStateBaseCpp.h"

APlayerBaseCpp::APlayerBaseCpp()
{
	PrimaryActorTick.bCanEverTick = true;

	BoxCollider = CreateDefaultSubobject<UBoxComponent>(TEXT("Box Collider"));
	RootComponent = BoxCollider;
	BoxCollider->SetupAttachment(RootComponent);
	BoxCollider->SetSimulatePhysics(true);

	BlockBoxCollider = CreateDefaultSubobject<UBoxComponent>(TEXT("Block Box Collider"));
	BlockBoxCollider->SetSimulatePhysics(false);
	BlockBoxCollider->SetGenerateOverlapEvents(false);
	BlockBoxCollider->SetupAttachment(RootComponent);
	
	SpriteFlipBook = CreateDefaultSubobject<UPaperFlipbookComponent>(TEXT("Flipbook"));
	SpriteFlipBook->SetSimulatePhysics(false);
	SpriteFlipBook->SetupAttachment(RootComponent);

	CameraArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("Camera Arm"));
	CameraArm->SetRelativeRotation(FRotator(0.0, -90.0, 0.0));
	CameraArm->SetAbsolute(true, false, false);
	CameraArm->SetupAttachment(RootComponent);

	FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("Follow Camera"));
	FollowCamera->SetProjectionMode(ECameraProjectionMode::Orthographic);
	FollowCamera->SetWorldRotation(FVector(0.0, 0.0, 0.0).Rotation());
	FollowCamera->SetupAttachment(CameraArm, USpringArmComponent::SocketName);

	Audio = CreateDefaultSubobject<UAudioComponent>(TEXT("Audio"));

	InventoryComponent = CreateDefaultSubobject<UInventoryComponentBaseCpp>(TEXT("Inventory Component"));
	AttackComponent = CreateDefaultSubobject<UAttackComponentBaseCpp>(TEXT("Attack Component"));
	HPComponent = CreateDefaultSubobject<UHPComponentBaseCpp>(TEXT("HP Component"));

	AnimState = EAnimEnum::E_IdleRight;
	ActionState = EActionEnum::E_Idle;

	MaxJumpCnt = 2;
	CurrentJumpCnt = 0;

	Tags.Add(FName("Player"));
}

void APlayerBaseCpp::BeginPlay()
{
	Super::BeginPlay();
	APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
	if (PlayerController == nullptr)
	{
		UE_LOG(LogTemp, Error, TEXT("No Player Controller found!"));
		return;
	}
	GetWorld()->GetFirstPlayerController()->Possess(this);
	CameraArm->SetWorldLocation(GetActorLocation());

	FScriptDelegate BlockBoxDelegate;
	BlockBoxDelegate.BindUFunction(this, FName("OnHitGround"));
	BlockBoxCollider->OnComponentHit.Add(BlockBoxDelegate);

	AttackComponent->SetParentPlayer(this);
	APlayerStateBaseCpp* CurrentPlayerState = Cast<APlayerStateBaseCpp>(GetWorld()->GetFirstPlayerController()->GetPlayerState<APlayerStateBaseCpp>());
	CurrentPlayerState->SetDamage();
	CurrentPlayerState->SetPlayerHP(100.0);
}

void APlayerBaseCpp::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	const FVector CurrentPlayerLocation = GetActorLocation();
	const FVector CurrentCameraArmLocation = CameraArm->GetComponentLocation();
	FVector Delta = FVector::ZeroVector;
	UpdateCameraLocation(CurrentCameraArmLocation, CurrentPlayerLocation, Delta);
	if (Delta != FVector::ZeroVector)
	{
		CameraArm->SetWorldLocation(CurrentCameraArmLocation + Delta);
	}
	
	if (IsJumping)
	{
		if (BlockBoxCollider->GetCollisionEnabled() == ECollisionEnabled::NoCollision)
		{
			BlockBoxCollider->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
		}
	}
	else
	{
		if (BlockBoxCollider->GetCollisionEnabled() == ECollisionEnabled::QueryAndPhysics)
		{
			BlockBoxCollider->SetCollisionEnabled(ECollisionEnabled::NoCollision);
		}
	}
}

void APlayerBaseCpp::MoveRight(float Axis)
{
	if (!AbilityBusy && FMath::Abs(Axis) < 0.5)
	{
		if (ActionState != EActionEnum::E_Idle)
		{
			const EAnimEnum TargetAnimState = GetAnimStateByAction(ActionState, EActionEnum::E_Idle);
			UpdateAnimStateMachine(TargetAnimState);
		}
		return;
	}
	else if (!AbilityBusy && FMath::Abs(Axis) >= 0.5)
	{
		if (Axis > 0 && ActionState != EActionEnum::E_RunRight)
		{
			EAnimEnum TargetAnimState = EAnimEnum::E_None;
			TargetAnimState = GetAnimStateByAction(ActionState, EActionEnum::E_RunRight);
			UpdateAnimStateMachine(TargetAnimState);
		}
		else if (Axis < 0 && ActionState != EActionEnum::E_RunLeft)
		{
			EAnimEnum TargetAnimState = EAnimEnum::E_None;
			TargetAnimState = GetAnimStateByAction(ActionState, EActionEnum::E_RunLeft);
			UpdateAnimStateMachine(TargetAnimState);
		}
	}
	
	const FVector Direction(1.0, 0.0, 0.0);
	const FVector CurrentLocation = GetActorLocation();
	const FVector DeltaLocation = Axis * Direction * MoveSpeed * GetWorld()->DeltaTimeSeconds;
	SetActorLocation(CurrentLocation + DeltaLocation);
}

void APlayerBaseCpp::MoveJump()
{
	if (CurrentJumpCnt >= MaxJumpCnt)
	{
		return;
	}
	CurrentJumpCnt++;
	BoxCollider->AddImpulse(FVector::UpVector * JumpForce);
	IsJumping = true;
	if (EActionEnum::E_Jump != ActionState)
	{
		const EAnimEnum TargetAnimState = GetAnimStateByAction(ActionState, EActionEnum::E_Jump);
		UpdateAnimStateMachine(TargetAnimState);
	}
}

void APlayerBaseCpp::Attack()
{
	if (AbilityBusy)
	{
		return;
	}
	AbilityBusy = true;
	const EAnimEnum TargetAnimState = GetAnimStateByAction(ActionState, EActionEnum::E_Attack);
	UpdateAnimStateMachine(TargetAnimState);
	const FTimerDelegate AbilityDelegate = FTimerDelegate::CreateUObject(this, &APlayerBaseCpp::AbilityTimerCallback);
	GetWorldTimerManager().SetTimer(AbilityTimerHandle, AbilityDelegate, 0.2667f, false);
	const FTimerDelegate KillDelegate = FTimerDelegate::CreateUObject(this, &APlayerBaseCpp::KillTimerCallback);
	GetWorldTimerManager().SetTimer(KillTimerHandle, KillDelegate, 0.066667f, false);
	PlayAudio(ActionState);
}

void APlayerBaseCpp::AbilityTimerCallback()
{
	AbilityBusy = false;
	GetWorldTimerManager().ClearTimer(AbilityTimerHandle);
	const EAnimEnum TargetAnimState = GetAnimStateByAction(ActionState, EActionEnum::E_Idle);
	UpdateAnimStateMachine(TargetAnimState);
}

void APlayerBaseCpp::KillTimerCallback()
{
	GetWorldTimerManager().ClearTimer(KillTimerHandle);
	const FVector CurrentLocation = GetActorLocation();
	FVector CurrentDirection = FVector(1.0f, 0.0f, 0.0f);
	if (AnimState == EAnimEnum::E_AttackLeft)
	{
		CurrentDirection.X = -1.0f;
	}
	TArray<FHitResult> Results1;
	RayTraceMulti(CurrentLocation, CurrentLocation + CurrentDirection * KillRange, Results1);
	TArray<FHitResult> Results2;
	RayTraceMulti(CurrentLocation + FVector::UpVector * 4, (CurrentLocation + CurrentDirection * KillRange) + FVector::UpVector * 4, Results2);
	for (FHitResult R : Results2)
	{
		Results1.Add(R);
	}
	AttackComponent->Attack(Results1);
}

void APlayerBaseCpp::AttackPro()
{
	if (AbilityBusy)
	{
		return;
	}
	AbilityBusy = true;
	const FTimerDelegate AttackProDelegate = FTimerDelegate::CreateUObject(this, &APlayerBaseCpp::AbilityProTimerCallback);
	GetWorldTimerManager().SetTimer(AbilityProTimerHandle, AttackProDelegate, 0.75f, false);
	AttackComponent->AttackPro();
}

void APlayerBaseCpp::AbilityProTimerCallback()
{
	AbilityBusy = false;
	GetWorldTimerManager().ClearTimer(AbilityProTimerHandle);
}


EAnimEnum APlayerBaseCpp::GetAnimStateByAction(EActionEnum& CurrentAction, EActionEnum TargetAction)
{
	if (CurrentAction == TargetAction)
	{
		return AnimState;
	}
	EAnimEnum TargetAnimState = EAnimEnum::E_None;
	CurrentAction = TargetAction;
	
	switch (TargetAction)
	{
		case EActionEnum::E_RunRight:
			{
				TargetAnimState = EAnimEnum::E_RunRight;
				break;
			}
		case EActionEnum::E_RunLeft:
			{
				TargetAnimState = EAnimEnum::E_RunLeft;
				break;
			}
		case EActionEnum::E_Idle:
			{
				if (AnimState == EAnimEnum::E_RunRight || AnimState == EAnimEnum::E_AttackRight || AnimState == EAnimEnum::E_JumpRight)
				{
					TargetAnimState = EAnimEnum::E_IdleRight;
				}
				else if (AnimState == EAnimEnum::E_RunLeft || AnimState == EAnimEnum::E_AttackLeft || AnimState == EAnimEnum::E_JumpLeft)
				{
					TargetAnimState = EAnimEnum::E_IdleLeft;
				}
				break;
			}
		case EActionEnum::E_Jump:
			{
				if (AnimState == EAnimEnum::E_IdleRight || AnimState == EAnimEnum::E_RunRight)
				{
					TargetAnimState = EAnimEnum::E_JumpRight;
				}
				else if (AnimState == EAnimEnum::E_IdleLeft || AnimState == EAnimEnum::E_RunLeft)
				{
					TargetAnimState = EAnimEnum::E_JumpLeft;
				}
				break;
			}
		case  EActionEnum::E_Attack:
			{
				if (AnimState == EAnimEnum::E_IdleRight || AnimState == EAnimEnum::E_RunRight)
				{
					TargetAnimState = EAnimEnum::E_AttackRight;
				}
				else if (AnimState == EAnimEnum::E_IdleLeft || AnimState == EAnimEnum::E_RunLeft)
				{
					TargetAnimState = EAnimEnum::E_AttackLeft;
				}
				break;
			}
		case EActionEnum::E_Dead:
			{
				if (AnimState != EAnimEnum::E_DeadRight)
				{
					TargetAnimState = EAnimEnum::E_DeadRight;
				}
				else if (AnimState != EAnimEnum::E_DeadLeft)
				{
					TargetAnimState = EAnimEnum::E_DeadLeft;
				}
				break;
			}
		default:
			break;
	}
	return TargetAnimState;
}

void APlayerBaseCpp::OnHitGround(UPrimitiveComponent* HitComponent, AActor* OtherActor, UPrimitiveComponent* OtherComponent, FVector NormalImpulse, const FHitResult& Hit)
{
	IsJumping = false;
	CurrentJumpCnt = 0;
	if (EActionEnum::E_Idle != ActionState)
	{
		const EAnimEnum TargetAnimState = GetAnimStateByAction(ActionState, EActionEnum::E_Idle);
		if (TargetAnimState != AnimState)
		{
			UpdateAnimStateMachine(TargetAnimState);
		}
	}
}

void APlayerBaseCpp::UpdateCameraLocation(const FVector& CurrentCameraArmLocation, const FVector& CurrentPlayerLocation, FVector& Delta)
{
	const FVector CameraArmToPlayer = CurrentPlayerLocation - CurrentCameraArmLocation;
	if (CameraArmToPlayer.X > WidthMargin)
	{
		Delta.X = CameraArmToPlayer.X - WidthMargin;
	}
	else if (CameraArmToPlayer.X < -WidthMargin)
	{
		Delta.X = CameraArmToPlayer.X + WidthMargin;
	}
	if (CameraArmToPlayer.Z > TopMargin)
	{
		Delta.Z = CameraArmToPlayer.Z - TopMargin;
	}
	else if (CameraArmToPlayer.Z < -BottomMargin)
	{
		Delta.Z = CameraArmToPlayer.Z + BottomMargin;
	}
}

float APlayerBaseCpp::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	APlayerStateBaseCpp* CurrentPlayerState = GetWorld()->GetFirstPlayerController()->GetPlayerState<APlayerStateBaseCpp>();
	const float CurrentPlayerHP = CurrentPlayerState->GetPlayerHP();
	if (CurrentPlayerHP - DamageAmount <= 0.0 && CurrentPlayerHP > 0.0f)
	{
		AGameModeBaseCpp* CurrentGameMode = Cast<AGameModeBaseCpp>(UGameplayStatics::GetGameMode(GetWorld()));
		if (CurrentGameMode != nullptr)
		{
			CurrentGameMode->SetDeadUITimer();
		}
	}
	HPComponent->DecreaseHP(DamageAmount);
	return 0.0f;
}

UInventoryComponentBaseCpp* APlayerBaseCpp::GetInventoryComponent() const
{
	return InventoryComponent;
}

UHPComponentBaseCpp* APlayerBaseCpp::GetHPComponent() const
{
	return HPComponent;
}

UAttackComponentBaseCpp* APlayerBaseCpp::GetAttackComponent() const
{
	return AttackComponent;
}

